﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using CoolCode.Entities;
using CoolCode.Data.Linq;
using CoolCode.Linq;

namespace CoolCode.Services
{
    public class InspectionService : ServiceBase, IInspectionService
    {
        #region Data Access Object

        private IRepository<Inspection> _InspectionDao;
        public IRepository<Inspection> InspectionDao
        {
            get
            {
                if (_InspectionDao == null)
                {
                    _InspectionDao = this.CreateRepository<Inspection>();
                }
                return _InspectionDao;
            }
            set
            {
                _InspectionDao = value;
            }
        }

        private IRepository<Production> _ProductionDao;
        public IRepository<Production> ProductionDao
        {
            get
            {
                if (_ProductionDao == null)
                {
                    _ProductionDao = this.CreateRepository<Production>();
                }
                return _ProductionDao;
            }
            set
            {
                _ProductionDao = value;
            }
        }

        private IRepository<Checker> _CheckerDao;
        public IRepository<Checker> CheckerDao
        {
            get
            {
                if (_CheckerDao == null)
                {
                    _CheckerDao = this.CreateRepository<Checker>();
                }
                return _CheckerDao;
            }
            set
            {
                _CheckerDao = value;
            }
        }

        private IRepository<Outbound> _OutboundDao;
        public IRepository<Outbound> OutboundDao
        {
            get
            {
                if (_OutboundDao == null)
                {
                    _OutboundDao = this.CreateRepository<Outbound>();
                }
                return _OutboundDao;
            }
            set
            {
                _OutboundDao = value;
            }
        }

        private IRepository<Inbound> _InboundDao;
        public IRepository<Inbound> InboundDao
        {
            get
            {
                if (_InboundDao == null)
                {
                    _InboundDao = this.CreateRepository<Inbound>();
                }
                return _InboundDao;
            }
            set
            {
                _InboundDao = value;
            }
        }

        #endregion

        public InspectionService()
            : base()
        {
            this.LoadWith<Outbound>(c => c.Inspection)
                .LoadWith<Inbound>(c => c.Inspection)
                .EndLoad();
        }

        private ICriterionService _criterionService;

        public InspectionService(ICriterionService criterionService)
            : this()
        {
            _criterionService = criterionService;
        }

        public PaginatedList<Inspection> Paging(IQueryBuilder<Inspection> queryBuilder, PageParam p)
        {
            var query = this.InspectionDao.Query(queryBuilder.Expression)
                .OrderBy(c => c.ProductSpec).ThenBy(c => c.CheckDate).ThenBy(c => c.ProductID);
            var list = query.AsPaginatedList(p);
            //list.ForEach((c, i) => c.Line = (i + 1));
            RetrieveCriterion(list, _criterionService);
            return list;
        }

        public List<Inspection> List(IQueryBuilder<Inspection> queryBuilder)
        {
            var query = this.InspectionDao.Query(queryBuilder.Expression)
                .OrderBy(c => c.CheckDate).ThenBy(c => c.CheckDate).ThenBy(c => c.ProductID);
            var list = query.ToList();
            //list.ForEach((c, i) => c.Line = (i + 1));
            RetrieveCriterion(list, _criterionService);
            return list;
        }

        public InspectionSummary ListSummaryBySpec(IQueryBuilder<Inspection> queryBuilder)
        {
            InspectionSummary result = new InspectionSummary();
            var query = this.InspectionDao.Query(queryBuilder.Expression);
            int total = query.Count();
            if (total > 0)
            {
                var summary = query.GroupBy(c => c.ProductSpec)
                    .Select(c => new { Key = c.Key, Count = c.Count() }).ToList();
                summary.ForEach(c => result.Add(new SummaryInfo { Key = c.Key, Count = c.Count, Persent = c.Count / (double)total }));
            }
            return result;
        }

        public InspectionSummary Summary(IQueryBuilder<Inspection> detail, IQueryBuilder<Inspection> group)
        {
            InspectionSummary result = new InspectionSummary();
            int total_group = Count(group);
            if (total_group > 0)
            {
                var query = this.InspectionDao.Query(detail.Expression);
                var summary = query.GroupBy(c => c.ProductSpec)
                   .Select(c => new { Key = c.Key, Count = c.Count() }).ToList();
                //int total_filter = query.Count();

                int total_filter = summary.Sum(c => c.Count);
                result.Total = total_group;
                //result.Add(new SummaryInfo { Key = "All", Count = total_filter, Persent = total_filter / total_group });
                summary.ForEach(c => result.Add(new SummaryInfo { Key = c.Key, Count = c.Count, Persent = c.Count / (double)total_group }));
            }
            return result;
        }

        public int Count(IQueryBuilder<Inspection> queryBuilder)
        {
            var query = this.InspectionDao.Query(queryBuilder.Expression);
            var count = query.Count();
            return count;
        }

        public bool Contains(string productID)
        {
            return this.InspectionDao.Contains(c => c.ProductID == productID);
        }

        public Inspection Get(string productID)
        {
            return this.InspectionDao.Get(c => c.ProductID == productID);
        }

        public void SaveInspection(Inspection entity, Log log)
        {
            entity.Year = entity.CheckDate.Year.ToString();
            entity.Month = entity.CheckDate.Month.ToString();
            this.InspectionDao.Delete(c => c.ID == entity.ID);
            this.InspectionDao.Insert(entity);
            this.LogAndSubmit(log);
        }

        public void SaveInspections(List<Inspection> list, Log log)
        {
            list.ForEach(entity =>
            {
                entity.Year = entity.CheckDate.Year.ToString();
                entity.Month = entity.CheckDate.Month.ToString();
            });
            this.InspectionDao.Insert(list);
            this.LogAndSubmit(log);
        }

        public Guid Outbound(List<string> productIDList, string user, DateTime date, Log log)
        {
            var outProducts = this.InspectionDao.List(c => productIDList.Contains(c.ProductID));
            outProducts.ForEach(c => c.Status = ProductStatus.Out.GetValue());

            string status = OutboundStatus.Normal.GetAttachedString();
            Guid guid = Guid.NewGuid();
            DateTime time = DateTime.Now;
            var list = productIDList.Select(c =>
             new Outbound
            {
                ProductID = c,
                Status = status,
                UID = guid,
                Date = date,
                CreateUser = user,
                UpdateUser = user,
                CreateDate = time,
                UpdateDate = time,
                Year = date.Year.ToString(),
                Month = date.Month.ToString(),
            });
            this.OutboundDao.Insert(list);
            this.LogAndSubmit(log);
            return guid;
        }

        public Guid Cancel(List<string> productIDList, string user, DateTime date, Log log)
        {
            Guid guid = Guid.NewGuid();
            return guid;
        }

        public PaginatedList<Inspection> PagingOutbound(Guid guid, PageParam p)
        {
            var query = this.OutboundDao.Query(c => c.UID == guid && c.Status == OutboundStatus.Normal.GetAttachedString())
                .OrderBy(c => c.Inspection.ProductSpec).ThenBy(c => c.Inspection.CheckDate);
            var result = query.AsPaginatedList(p);
            var list = result.Select(c => c.Inspection);
            //list.ForEach((c, i) => c.Line = (i + 1));
            RetrieveCriterion(list, _criterionService);
            return list;
        }

        public List<Inspection> ListOutbound(Guid guid)
        {
            var query = this.OutboundDao.Query(c => c.UID == guid && c.Status == OutboundStatus.Normal.GetAttachedString())
                .OrderBy(c => c.Inspection.ProductSpec).ThenBy(c => c.Inspection.CheckDate);
            var list = query.Select(c => c.Inspection).ToList();
            //list.ForEach((c, i) => c.Line = (i + 1));
            RetrieveCriterion(list, _criterionService);
            return list;
        }

        public PaginatedList<SearchItem> Paging(IQueryBuilder<Outbound> queryBuilder, PageParam p)
        {
            var query1 = from o in this.OutboundDao.Query(queryBuilder.Expression)
                         from c in this.InspectionDao
                         where o.ProductID == c.ProductID
                         select new { UID = o.UID, Date = o.Date, ProductID = o.ProductID, ProductSpec = c.ProductSpec };
            var query2 = from q in query1
                         group new SearchItem { Date = q.Date, UID = q.UID.ToString() } by q.Date;


            //var query =(from q in( from g in  ( from o in this.OutboundDao.Query(queryBuilder.Expression)
            //           from  c in this.InspectionDao 
            //                       where  o.ProductID == c.ProductID 
            //     select new {O= o, C=c} 
            //      )                  
            //     group g by g.O.Date 
            //     )
            //     orderby q.Key 
            //     select new  {
            //        UID = q.First().O.UID.ToString(),
            //        Date = o.Key,
            //        Title = string.Join(",", q. q.Select(k => k.Inspection.ProductSpec).Distinct().ToArray()),
            //        Tips = string.Join(",", o.Select(k => k.Inspection.ProductID).ToArray()),
            //    };

            var query = this.OutboundDao.Query(queryBuilder.Expression)
                .GroupBy(c => c.Date)
                .OrderByDescending(c => c.Key)
                .Select(c => new SearchItem
                {
                    UID = c.First().UID.ToString(),
                    Date = c.Key,
                    Title = string.Join(",", c.Select(k => k.Inspection.ProductSpec).Distinct().ToArray()),
                    Tips = string.Join(",", c.Select(k => k.Inspection.ProductID).ToArray()),
                });
            var list = query.AsPaginatedList(p);
            list.ForEach(c =>
            {
                c.Content = c.Tips.Length > 80 ? c.Tips.Substring(0, 77) + "..." : c.Tips;
                c.Url = string.Format("OutboundResult.aspx?key={0}", c.UID);
            });

            return list;
        }

        public List<ValueText> ListStatus(bool includeAll)
        {
            var query = Enum<ProductStatus>.AsEnumerable();
            if (!includeAll)
            {
                query = query.Where(c => c.GetValue() != ProductStatus.All.GetValue());
                //return Enum<ProductStatus>.Select(c => new ValueText(c.GetValue(), c.GetText())).ToList();
            }
            //list.Add(ProductStatus.In.GetAttachedString(),

            return query.Select(c => new ValueText(c.GetValue(), c.GetText())).ToList();
        }

        public List<string> ListExistedProductID(List<string> productIDList)
        {
            var products = this.InspectionDao.Query(c => productIDList.Contains(c.ProductID))
                .Select(c => c.ProductID).ToList();
            return products;
        }

        public Guid ImportInspection(List<Inspection> list, string user, Log log)
        {
            Guid guid = Guid.NewGuid();
            string boundStatus = OutboundStatus.Normal.GetAttachedString();
            string storedStatus = ProductStatus.In.GetAttachedString();
            DateTime time = DateTime.Now;
            var track = list.Select(c =>
             new Inbound
            {
                ProductID = c.ProductID,
                Status = boundStatus,
                UID = guid,
                Date = time.Date,
                CreateUser = user,
                UpdateUser = user,
                CreateDate = time,
                UpdateDate = time,
            });
            list.ForEach(c =>
            {
                c.LotNo = time.ToString("yyyy-MM-dd HH:mm:ss");
                c.Status = storedStatus;
                c.CreateUser = user;
                c.UpdateUser = user;
                c.CreateDate = time;
                c.UpdateDate = time;
                c.Year = c.CheckDate.Year.ToString();
                c.Month = c.CheckDate.Month.ToString();
            });

            this.InspectionDao.Insert(list);
            this.InboundDao.Insert(track);
            this.LogAndSubmit(log);
            return guid;
        }

        private static void RetrieveCriterion(List<Inspection> list, ICriterionService service)
        {
            if (service == null) return;
            List<ProductCriterion> criterions = service.List();
            RetrieveCriterion(list, criterions);
        }

        private static void RetrieveCriterion(List<Inspection> list, List<ProductCriterion> criterions)
        {
            list.ForEach((c, i) =>
            {
                var criterion = criterions.FirstOrDefault(cr => cr.ProductSpec == c.ProductSpec);
                if (criterion != null)
                {
                    c.PassODFar = c.ODFar.InRange(criterion.MinODFar, criterion.MaxODFar);
                    c.PassODClose = c.ODClose.InRange(criterion.MinODClose, criterion.MaxODClose);
                    c.PassConeHeight = c.ConeHeight.InRange(criterion.MinConeHeight, criterion.MaxConeHeight);
                    c.PassSacculusLength = c.SacculusLength.InRange(criterion.MinSacculusLength, criterion.MaxSacculusLength);
                    c.PassSacculusStretch = c.SacculusStretch.InRange(criterion.MinSacculusStretch, criterion.MaxSacculusStretch);
                    c.PassODStressTest2 = c.ODStressTest2.InRange(criterion.MinODStressTest2, criterion.MaxODStressTest2);
                    c.PassStressTestLength = c.StressTestLength.InRange(criterion.MinStressTestLength, criterion.MaxStressTestLength);
                }
                c.Line = (i + 1);
            });
        }

        #region ITrackService<Inspection> Members

        IRepository<Inspection> ITrackService<Inspection>.Repository
        {
            get
            {
                return InspectionDao;
            }
        }

        public ITrackService Current
        {
            get { return this; }
        }

        #endregion

        #region 统计

        public Tuple<DateTime, DateTime> GetCheckDateRange()
        {
            DateTime minDate = this.ProductionDao.Min(c => c.CreateDate);
            DateTime maxDate = this.ProductionDao.Max(c => c.CreateDate);
            return new Tuple<DateTime, DateTime>(minDate, maxDate);
        }

        public List<StatisticalProduct> Statistics(int yearFrom, int yearTo)
        {
            ProductCriterionProvider provider = new ProductCriterionProvider(_criterionService.List());
            DateTime minDate = new DateTime(yearFrom, 1, 1);
            DateTime maxDate = new DateTime(yearTo, 12, 31, 23, 59, 59, 999);
            var query = this.InspectionDao.Where(c => c.CreateDate >= minDate && c.CreateDate <= maxDate);
            List<StatisticalProduct> result = query.Select(c => c.Year).Distinct().Select(c => new StatisticalProduct { GroupBy = c }).ToList();

            statisticsSubTotal(result, query, c => c.Year, provider);
            statisticsPersent(result, query, c => c.Year, provider);
            return result;
        }

        public List<StatisticalProduct> Statistics(int year)
        {
            ProductCriterionProvider provider = new ProductCriterionProvider(_criterionService.List());
            DateTime minDate = new DateTime(year, 1, 1);
            DateTime maxDate = new DateTime(year, 12, 31, 23, 59, 59, 999);
            var query = this.InspectionDao.Where(c => c.CreateDate >= minDate && c.CreateDate <= maxDate);
            List<StatisticalProduct> result = query.Select(c => c.Month).Distinct().Select(c => new StatisticalProduct { GroupBy = c }).ToList();

            statisticsSubTotal(result, query, c => c.Month, provider);
            statisticsPersent(result, query, c => c.Month, provider);
            return result;
        }

        private IDictionary<string, int> statisticsPassProduct<T>(IQueryable<Inspection> query,
          System.Linq.Expressions.Expression<Func<Inspection, string>> groupBy, ProductCriterionProvider provider)
            where T : IProductCriterion
        {
            var queryPass = query.Where(provider.CreatePassExpression<T>())
                 .GroupBy(groupBy)
                 .Select(c => new { GroupBy = c.Key, Count = c.Count() });
            var pass = queryPass.ToDictionary(c => c.GroupBy, c => c.Count);
            return pass;
        }

        private void statisticsSubTotal(List<StatisticalProduct> result, IQueryable<Inspection> query,
          System.Linq.Expressions.Expression<Func<Inspection, string>> groupBy, ProductCriterionProvider provider)
        {
            var passODFar = statisticsPassProduct<ODFarCriterion>(query, groupBy, provider);
            var passODClose = statisticsPassProduct<ODCloseCriterion>(query, groupBy, provider);
            var passConeHeight = statisticsPassProduct<ConeHeightCriterion>(query, groupBy, provider);
            var passSacculusLength = statisticsPassProduct<SacculusLengthCriterion>(query, groupBy, provider);
            var passSacculusStretch = statisticsPassProduct<SacculusStretchCriterion>(query, groupBy, provider);
            var passODStressTest2 = statisticsPassProduct<ODStressTest2Criterion>(query, groupBy, provider);
            var passStressTestLength = statisticsPassProduct<StressTestLengthCriterion>(query, groupBy, provider);

            result.ForEach(c =>
            {
                c.PassODFar = passODFar.GetValue(c.GroupBy, () => 0);
                c.PassODClose = passODClose.GetValue(c.GroupBy, () => 0);
                c.PassConeHeight = passConeHeight.GetValue(c.GroupBy, () => 0);
                c.PassSacculusLength = passSacculusLength.GetValue(c.GroupBy, () => 0);
                c.PassSacculusStretch = passSacculusStretch.GetValue(c.GroupBy, () => 0);
                c.PassODStressTest2 = passODStressTest2.GetValue(c.GroupBy, () => 0);
                c.PassStressTestLength = passStressTestLength.GetValue(c.GroupBy, () => 0);
            });
        }

        private void statisticsPersent(List<StatisticalProduct> result, IQueryable<Inspection> query,
          System.Linq.Expressions.Expression<Func<Inspection, string>> groupBy, ProductCriterionProvider provider)
        {
            var queryPass = query.GroupBy(groupBy)
                 .Select(c => new { GroupBy = c.Key, Count = c.Count() });
            var groupTotal = queryPass.ToDictionary(c => c.GroupBy, c => c.Count);
            Func<string, int, decimal> calculatePersent = (group, subTotal) =>
            {
                int total = groupTotal.GetValue(group, () => 0);
                if (total == 0) return 0m;
                return subTotal / (decimal)total;
            };

            result.ForEach(c =>
            {
                c.SubTotal = groupTotal.GetValue(c.GroupBy, () => 0); 
                c.PersentOfPassODFar = calculatePersent(c.GroupBy, c.PassODFar);
                c.PersentOfPassODClose = calculatePersent(c.GroupBy, c.PassODClose);
                c.PersentOfPassConeHeight = calculatePersent(c.GroupBy, c.PassConeHeight);
                c.PersentOfPassSacculusLength = calculatePersent(c.GroupBy, c.PassSacculusLength);
                c.PersentOfPassSacculusStretch = calculatePersent(c.GroupBy, c.PassSacculusStretch);
                c.PersentOfPassODStressTest2 = calculatePersent(c.GroupBy, c.PassODStressTest2);
                c.PersentOfPassStressTestLength = calculatePersent(c.GroupBy, c.PassStressTestLength);
            });
        }

        #endregion

    }
}
